//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_Script.h".
//-------------------------------------------------------------------

#ifndef _UXS_SCRIPT_ENGINE_H
#define _UXS_SCRIPT_ENGINE_H

#pragma once

#include "ScriptTypes.h"

namespace UX_STL
{
namespace UXScript
{

	
	//
	// Script Engine
	//

	struct ScriptEngine
	{
	// variables
	private:
		Ptr< AngelScript::asIScriptEngine >	_engine;


	// methods
	private:
		ScriptEngine (const ScriptEngine &) {}
		void operator = (const ScriptEngine &) {}

	public:
		ScriptEngine ();
		ScriptEngine (AngelScript::asIScriptEngine *se);
		~ScriptEngine ();

		AngelScript::asIScriptEngine *			Get ()					{ return _engine; }
		AngelScript::asIScriptEngine const *	Get ()	const			{ return _engine; }

		AngelScript::asIScriptEngine *			operator -> ()			{ return _engine; }
		AngelScript::asIScriptEngine const *	operator -> () const	{ return _engine; }

		template <typename T, typename R>
		bool Run (uni_c_string script, uni_c_string entry, T arg, OUT R &result);
		bool Run (uni_c_string script, uni_c_string entry);
		
		template <typename T, typename R>
		bool RunFromFile (uni_c_string filename, uni_c_string entry, T arg, OUT R &result);
		bool RunFromFile (uni_c_string filename, uni_c_string entry);

		template <typename T>
		void AddFunction (T func, uni_c_string name);

		//template <typename T>
		//void AddFunctionTemplate (T func, uni_c_string name);

		template <typename T>
		void AddProperty (T &var, uni_c_string name);
		
		template <typename T>
		void AddConstProperty (const T &var, uni_c_string name);

		void AddEnumValue (uni_c_string name, uni_c_string valueName, int value);

		void SetNamespace (uni_c_string name);
		void SetDefaultNamespace ();


		static bool _CheckError (int err, uni_c_string asFunc, uni_c_string func, uni_c_string file, int line);

	private:
		static void _MessageCallback (const AngelScript::asSMessageInfo *msg, void *param);
	};


	
/*
=================================================
	constructor
=================================================
*/
	inline ScriptEngine::ScriptEngine ()
	{
		using namespace AngelScript;

		_engine = asCreateScriptEngine( ANGELSCRIPT_VERSION );

		_engine->SetMessageCallback( asFUNCTION( _MessageCallback ), 0, asCALL_CDECL );
	}
	
/*
=================================================
	constructor
=================================================
*/
	inline ScriptEngine::ScriptEngine (AngelScript::asIScriptEngine *se)
	{
		CHECK_ERR( se != null, );

		_engine = se;
		_engine->AddRef();
	}
	
/*
=================================================
	destructor
=================================================
*/
	inline ScriptEngine::~ScriptEngine ()
	{
		_engine->Release();
	}
	
/*
=================================================
	AddFunction
=================================================
*/
	template <typename T>
	inline void ScriptEngine::AddFunction (T func, uni_c_string name)
	{
		using namespace AngelScript;

		string	s;
		GlobalFunction<T>::GetDescriptor( s, name );

		AS_CALL( _engine->RegisterGlobalFunction( s.cstr(), asFUNCTION( *func ), asCALL_CDECL ) );
	}
	
/*
=================================================
	AddProperty
=================================================
*/
	template <typename T>
	inline void ScriptEngine::AddProperty (T &var, uni_c_string name)
	{
		string	s;
		ScriptTypeInfo<T>::Name( s );
		s << ' ' << name;

		AS_CALL( _engine->RegisterGlobalProperty( s.cstr(), &var ) );
	}
	
/*
=================================================
	AddConstProperty
=================================================
*/
	template <typename T>
	inline void ScriptEngine::AddConstProperty (const T &var, uni_c_string name)
	{
		string	s("const ");
		ScriptTypeInfo<T>::Name( s );
		s << ' ' << name;

		AS_CALL( _engine->RegisterGlobalProperty( s.cstr(), (void *) &var ) );
	}
	
/*
=================================================
	AddEnumValue
=================================================
*/
	inline void ScriptEngine::AddEnumValue (uni_c_string name, uni_c_string valueName, int value)
	{
		using namespace AngelScript;

		int	res = _engine->RegisterEnum( name.cstr() );

		if ( res < 0 and res != asALREADY_REGISTERED ) {
			AS_CALL( res );
			return;
		}

		AS_CALL( _engine->RegisterEnumValue( name.cstr(), valueName.cstr(), value ) );
	}
	
/*
=================================================
	SetNamespace
=================================================
*/
	inline void ScriptEngine::SetNamespace (uni_c_string name)
	{
		AS_CALL( _engine->SetDefaultNamespace( name.cstr() ) );
	}
	
/*
=================================================
	SetDefaultNamespace
=================================================
*/
	inline void ScriptEngine::SetDefaultNamespace ()
	{
		SetNamespace( "" );
	}

/*
=================================================
	Run
=================================================
*/
	template <typename T, typename R>
	inline bool ScriptEngine::Run (uni_c_string script, uni_c_string entry, T arg, OUT R &result)
	{
		using namespace AngelScript;

		string	s;
		GlobalFunction< R (*) (T) >::GetDescriptor( s, entry.cstr() );
		
		asIScriptModule *	mod = _engine->GetModule( "def", asGM_CREATE_IF_NOT_EXISTS );
		mod->AddScriptSection( "def_script", script.cstr() );
		
		AS_CALL_R( mod->Build() );
		
		asIScriptContext *	ctx = _engine->CreateContext();
		
		asIScriptFunction * func = mod->GetFunctionByDecl( s.cstr() );
		CHECK_ERR( func != null );

		AS_CALL_R( ctx->Prepare( func ) );

		AS_CALL( ContextSetterGetter<T>::Set( ctx, 0, arg ) );

		const int exec_res = ctx->Execute();

		if ( exec_res == asEXECUTION_FINISHED )
		{
			result = ContextSetterGetter<R>::Get( ctx );
		}
		else
		if ( exec_res == asEXECUTION_EXCEPTION )
		{
			s.ClearMem();
			s	<< "Exception in function: "
				<< ctx->GetExceptionFunction()->GetName();

			const char *section = 0;
			int column = 0;
			int line = ctx->GetExceptionLineNumber( &column, &section );

			s << "(" << line << ", " << column << "):\n";
			s << section << "\n";
			s << ctx->GetExceptionString();

			RETURN_ERR( s );
		}
		else
		{
			RETURN_ERR( "AngelScript execution failed" );
		}

		AS_CALL( ctx->Release() );
		return true;
	}
	
/*
=================================================
	Run
=================================================
*/
	inline bool ScriptEngine::Run (uni_c_string script, uni_c_string entry)
	{
		using namespace AngelScript;

		string	s;
		GlobalFunction< void (*) () >::GetDescriptor( s, entry.cstr() );
		
		asIScriptModule *	mod = _engine->GetModule( "def", asGM_CREATE_IF_NOT_EXISTS );
		mod->AddScriptSection( "def_script", script.cstr() );
		
		AS_CALL_R( mod->Build() );
		
		asIScriptContext *	ctx = _engine->CreateContext();
		
		asIScriptFunction * func = mod->GetFunctionByDecl( s.cstr() );
		CHECK_ERR( func != null );

		AS_CALL_R( ctx->Prepare( func ) );
		
		const int exec_res = ctx->Execute();

		if ( exec_res == asEXECUTION_FINISHED )
		{
		}
		else
		if ( exec_res == asEXECUTION_EXCEPTION )
		{
			s.ClearMem();
			s	<< "Exception in function: "
				<< ctx->GetExceptionFunction()->GetName();

			const char *section = 0;
			int column = 0;
			int line = ctx->GetExceptionLineNumber( &column, &section );

			s << "(" << line << ", " << column << "):\n";
			s << section << "\n";
			s << ctx->GetExceptionString();

			RETURN_ERR( s );
		}
		else
		{
			RETURN_ERR( "AngelScript execution failed" );
		}

		AS_CALL( ctx->Release() );
		return true;
	}

/*
=================================================
	RunFromFile
=================================================
*/
	template <typename T, typename R>
	inline bool ScriptEngine::RunFromFile (uni_c_string filename, uni_c_string entry, T arg, OUT R &result)
	{
		File::HddRFile	file;
		CHECK_ERR( file.Open( filename ) );

		string		data;
		const usize	len = file.Size();

		data.Reserve( len+1 );
		file.Read( data.ptr(), len );
		data.SetLength( len );

		file.Close();

		return Run( data, entry, arg, result );
	}
	
/*
=================================================
	RunFromFile
=================================================
*/
	inline bool ScriptEngine::RunFromFile (uni_c_string filename, uni_c_string entry)
	{
		File::HddRFile	file;
		CHECK_ERR( file.Open( filename ) );

		string		data;
		const usize	len = file.Size();

		data.Reserve( len+1 );
		file.Read( data.ptr(), len );
		data.SetLength( len );

		file.Close();

		return Run( data, entry );
	}

/*
=================================================
	_MessageCallback
=================================================
*/
	inline void ScriptEngine::_MessageCallback (const AngelScript::asSMessageInfo *msg, void *param)
	{
		using namespace AngelScript;

		string			str("AngelScript message: ");
		e_log_msg::type	type = e_log_msg::DEBUG;

		str << msg->section << " (" << msg->row << ", " << msg->col << ") ";

		switch ( msg->type )
		{
			case asMSGTYPE_WARNING :		str << "WARNING ";	type = e_log_msg::DEF_WARNING;	break;
			case asMSGTYPE_INFORMATION :	str << "INFO ";		type = e_log_msg::INFO;			break;
			case asMSGTYPE_ERROR :			str << "ERROR ";	type = e_log_msg::DEF_ERROR;	break;
		}
			
		str << msg->message;

		LOG( str.cstr(), type );
	}
	
/*
=================================================
	_CheckError
=================================================
*/
	inline bool ScriptEngine::_CheckError (int err, uni_c_string asFunc, uni_c_string func, uni_c_string file, int line)
	{
		using namespace AngelScript;

		if ( err >= 0 )
			return true;

		string	str("AngelScript error: ");

#			define __AS_CASE_ERR( _val ) \
			case _val : str << #_val; break;

		switch ( err )
		{
			__AS_CASE_ERR( asERROR );
			__AS_CASE_ERR( asCONTEXT_ACTIVE );
			__AS_CASE_ERR( asCONTEXT_NOT_FINISHED );
			__AS_CASE_ERR( asCONTEXT_NOT_PREPARED );
			__AS_CASE_ERR( asINVALID_ARG );
			__AS_CASE_ERR( asNO_FUNCTION  );
			__AS_CASE_ERR( asNOT_SUPPORTED );
			__AS_CASE_ERR( asINVALID_NAME );
			__AS_CASE_ERR( asNAME_TAKEN );
			__AS_CASE_ERR( asINVALID_DECLARATION );
			__AS_CASE_ERR( asINVALID_OBJECT );
			__AS_CASE_ERR( asINVALID_TYPE );
			__AS_CASE_ERR( asALREADY_REGISTERED );
			__AS_CASE_ERR( asMULTIPLE_FUNCTIONS );
			__AS_CASE_ERR( asNO_MODULE );
			__AS_CASE_ERR( asNO_GLOBAL_VAR );
			__AS_CASE_ERR( asINVALID_CONFIGURATION );
			__AS_CASE_ERR( asINVALID_INTERFACE );
			__AS_CASE_ERR( asCANT_BIND_ALL_FUNCTIONS );
			__AS_CASE_ERR( asLOWER_ARRAY_DIMENSION_NOT_REGISTERED );
			__AS_CASE_ERR( asWRONG_CONFIG_GROUP );
			__AS_CASE_ERR( asCONFIG_GROUP_IS_IN_USE );
			__AS_CASE_ERR( asILLEGAL_BEHAVIOUR_FOR_TYPE );
			__AS_CASE_ERR( asWRONG_CALLING_CONV );
			__AS_CASE_ERR( asBUILD_IN_PROGRESS );
			__AS_CASE_ERR( asINIT_GLOBAL_VARS_FAILED );
			__AS_CASE_ERR( asOUT_OF_MEMORY );

			default :
				str << "code: 0x" << string().FormatI( err, 10 );
				break;
		}

#			undef __AS_CASE_ERR
			
		str << ", in " << asFunc;
		str << ", function: " << func;

		LOG_EXT( str.cstr(), e_log_msg::ERROR_2, file, line );
		return false;
	}
	
//-------------------------------------------------------------------

}	// UXScript
}	// UX_STL

#endif	// _UXS_SCRIPT_ENGINE_H